﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WcfContrib.Utils;
using System.Threading;

namespace WcfContrib.Extensions.Caching
{
    /// <summary>
    /// Represents a cache provider which caches items to the IOutputCacheStore attached to it
    /// The provider purges the items using a timer defined with a certain interval
    /// </summary>
    public class IntervalBasedCacheProvider : IOutputCacheProvider, IDisposable
    {
        IOutputCacheStore store;
        int purgeTimerIntervalInMilliseconds;
        SynchronizedDictionary<OutputCacheItemKey, OutputCacheInterval> _keys;
        Timer _timer;
        bool _collecting;

        /// <summary>
        /// Initializes a new instance of the <see cref="IntervalBasedCacheProvider"/> class.
        /// </summary>
        /// <param name="purgeTimerIntervalInMilliseconds">The purge timer interval in milliseconds.</param>
        /// <param name="store">The output cache store.</param>
        public IntervalBasedCacheProvider(int purgeTimerIntervalInMilliseconds, IOutputCacheStore store)
        {
            this.store = store;
            this.purgeTimerIntervalInMilliseconds = purgeTimerIntervalInMilliseconds;

            InitState();
        }

        void InitState()
        {
            _keys = new SynchronizedDictionary<OutputCacheItemKey, OutputCacheInterval>();

            _timer = new Timer(new TimerCallback(CollectItemsCallback), null,
                purgeTimerIntervalInMilliseconds, purgeTimerIntervalInMilliseconds);
        }

        void CollectItemsCallback(object state)
        {
            if (!_collecting)
            {
                try
                {
                    _collecting = true;

                    List<OutputCacheItemKey> removeKeys = new List<OutputCacheItemKey>();

                    using (_keys.AcquireLock(false))
                    {
                        foreach (KeyValuePair<OutputCacheItemKey, OutputCacheInterval> item in _keys)
                        {
                            if (!item.Value.IsActive())
                            {
                                removeKeys.Add(item.Key);
                            }
                        }
                    }

                    if (removeKeys.Count > 0)
                    {
                        using (_keys.AcquireLock(true))
                        {
                            foreach (OutputCacheItemKey key in removeKeys)
                            {
                                _keys.PerformNonSynchronized(d => d.Remove(key));

                                store.RemoveItem(key);
                            }
                        }
                    }
                }
                finally
                {
                    _collecting = false;
                }
            }
        }

        void UnloadState()
        {
            _keys = null;
            using (_timer) { };
            _timer = null;
        }

        #region IOutputCacheProvider Members

        /// <summary>
        /// Adds the item to the provider.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="interval">The cache interval.</param>
        /// <param name="value">The value.</param>
        public void AddItem(OutputCacheItemKey key, OutputCacheInterval interval, object value)
        {
            interval.LastVisitedAt = interval.AddedAt = DateTime.Now;

            _keys[key] = interval;

            store.SetItem(key, value);
        }

        /// <summary>
        /// Tries to get the item from the provider.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="value">The value.</param>
        /// <returns>true if the item was found; otherwise, false</returns>
        public bool TryGetItem(OutputCacheItemKey key, out object value)
        {
            value = null;
            
            OutputCacheInterval interval;
            if (_keys.TryGetValue(key, out interval))
            {
                if (interval.IsActive())
                {
                    interval.LastVisitedAt = DateTime.Now;
                    return store.TryGetItem(key, out value);
                }
            }
            

            return false;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            UnloadState();
        }

        #endregion
    }
}
